/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "VertexBuffer.h"
#include "Hardware.h"
#include <logs/Log.h>
#include <Gluck.h>
#include <math/utl.h>

//TODO: added by Max, rewrite this Greg-s junk

namespace liba
{
namespace lib3d
{
namespace hard
{
namespace directx8
{

VertexBuffer::VertexBuffer(Hardware * hardware, bool dynamic )
:	Dependent( hardware ),
	valid_flag( false ),
	vertex_buffer( 0 ),
	vertices_count( 0 ),
	stride( 0 ),
	dynamic( dynamic ),
	vb_declaration( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 ),
	primitives_type( TRIANGLESLIST )
{}

VertexBuffer::~VertexBuffer()
{
	release();
}

bool VertexBuffer::is_valid()const
{
	return valid_flag;
}

bool VertexBuffer::update(
		const SimpleArray<float> & positions,
		const SimpleArray<float> & normals,
		const SimpleArray<float> & u_tangents,
		const SimpleArray<float> & v_tangents,
		const SimpleArray<float> & uv0,
		const SimpleArray<float> & uv1,
		const SimpleArray<float> & weights0,
		const SimpleArray<float> & weights1,
		const SimpleArray<float> & weights2,
		const SimpleArray<int> & indices0,
		const SimpleArray<int> & indices1,
		const SimpleArray<int> & indices2,
		const SimpleArray<int> & indices3
		)
{
	vb_declaration = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1;

	if( !hardware->is_operational() )
	{
		return true;
/*		vertices_count = 0;
		if ( vertex_buffer )
			vertex_buffer->Release(); vertex_buffer = 0;
		valid_flag = true;
*/
	}

	int vertices_count = positions.size()/3;
	if( positions.size() != vertices_count*3 )
		return false;
	stride = 3 * sizeof(float);
	stride += 3 * sizeof(float);
//	stride += 3 * sizeof(float);
//	stride += 3 * sizeof(float);
	stride += 2 * sizeof(float);

	bool skeleton = false;
	if( vertices_count != 0 &&
		weights0.size() == vertices_count &&
		weights1.size() == vertices_count &&
		weights2.size() == vertices_count &&
		indices0.size() == vertices_count &&
		indices1.size() == vertices_count &&
		indices2.size() == vertices_count &&
		indices3.size() == vertices_count )
	{
		skeleton = true;
		stride += 3 * sizeof(float); // for weights
		stride += 4 * sizeof(float); // for indices

		return false; // Hardware skeleton not supported yet
	}
	else
	{}

	if( !valid_flag || this->vertices_count != vertices_count )
	{
		if ( vertex_buffer )
			vertex_buffer->Release(); vertex_buffer = 0;

		this->vertices_count = vertices_count;
		if( vertices_count == 0 )
		{
			valid_flag = true;
			return true;
		}

		HRESULT hr;
		if( dynamic )
			hr = hardware->get_device()->CreateVertexBuffer( stride*vertices_count, D3DUSAGE_WRITEONLY/* | D3DUSAGE_DYNAMIC*/, vb_declaration, D3DPOOL_DEFAULT, &vertex_buffer, NULL );
		else
			hr = hardware->get_device()->CreateVertexBuffer( stride*vertices_count, D3DUSAGE_WRITEONLY, vb_declaration, D3DPOOL_MANAGED, &vertex_buffer, NULL );
//		logs::log() << "create_internal_vertex_buffer() result: " << hr << "\n";
		if ( !vertex_buffer )
			throw Gluck(__FUNCTION__, "create_internal_vertex_buffer() failed");
	}
	if( vertices_count == 0 )
	{
		valid_flag = true;
		return true;
	}

	DWORD size = 0;
	float * data = 0;
	HRESULT hr = vertex_buffer->Lock( 0, 0, (void**) &data, /*dynamic ? D3DLOCK_DISCARD :*/ 0 );
	if ( FAILED(hr) )
		throw Gluck(__FUNCTION__, "vertex_buffer->Lock() failed");

	SimpleArray<float>::const_iterator posit = positions.begin();
	SimpleArray<float>::const_iterator norit = normals.begin();
	SimpleArray<float>::const_iterator u_tanit = u_tangents.begin();
	SimpleArray<float>::const_iterator v_tanit = v_tangents.begin();
	SimpleArray<float>::const_iterator uv0it = uv0.begin();
	SimpleArray<float>::const_iterator wei0it = weights0.begin();
	SimpleArray<float>::const_iterator wei1it = weights1.begin();
	SimpleArray<float>::const_iterator wei2it = weights2.begin();
	SimpleArray<int>::const_iterator ind0it = indices0.begin();
	SimpleArray<int>::const_iterator ind1it = indices1.begin();
	SimpleArray<int>::const_iterator ind2it = indices2.begin();
	SimpleArray<int>::const_iterator ind3it = indices3.begin();
	for( int i = 0; i < vertices_count; ++i )
	{
		*data++ = *posit++;
		*data++ = *posit++;
		*data++ = *posit++;

		if( normals.size() != vertices_count*3 )
		{
			*data++ = 0;
			*data++ = 0;
			*data++ = 1;
		}
		else
		{
			*data++ = *norit++;
			*data++ = *norit++;
			*data++ = *norit++;
		}

		if( uv0.size() != vertices_count*2 )
		{
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *uv0it++;
			*data++ = *uv0it++;
		}

/*		if( u_tangents.size() != vertices_count*3 )
		{
			*data++ = 0;
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *u_tanit++;
			*data++ = *u_tanit++;
			*data++ = *u_tanit++;
		}

		if( v_tangents.size() != vertices_count*3 )
		{
			*data++ = 0;
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *v_tanit++;
			*data++ = *v_tanit++;
			*data++ = *v_tanit++;
		}
*/		if( skeleton )
		{
			*data++ = *wei0it++;
			*data++ = *wei1it++;
			*data++ = *wei2it++;

			*data++ = *ind0it++ * 4;
			*data++ = *ind1it++ * 4;
			*data++ = *ind2it++ * 4;
			*data++ = *ind3it++ * 4;
///			DWORD * ddata = reinterpret_cast<DWORD *>( data++ );
///			int i3 = 3 * 4;//*ind3it++ * 4;
///			int i2 = 2 * 4;//*ind2it++ * 4;
///			int i1 = 1 * 4;//*ind1it++ * 4;
///			int i0 = 0 * 4;//*ind0it++ * 4;
///			*ddata = (i3 << 24) + (i2 << 16) + (i1 << 8) + i0;
		}
	}
	hr = vertex_buffer->Unlock();

	valid_flag = true;
	return true;
}

bool VertexBuffer::update(
					const SimpleArray<float> & positions,
					const SimpleArray<float> & normals,
					const SimpleArray<float> & colors,
					const SimpleArray<float> & uv0,
					const SimpleArray<float> & uv1
					)
{
	if( normals.size() )
		vb_declaration = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_DIFFUSE | D3DFVF_TEX1;
	else
		vb_declaration = D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1;

	if( !hardware->is_operational() )
	{
		return true;
		/*		vertices_count = 0;
		if ( vertex_buffer )
		vertex_buffer->Release(); vertex_buffer = 0;
		valid_flag = true;
		*/
	}

	int vertices_count = positions.size()/3;
	if( positions.size() != vertices_count*3 )
		return false;
	stride = 3 * sizeof(float);
	if( normals.size() )
		stride += 3 * sizeof(float);
	stride += 4 * sizeof(unsigned char);
	//	stride += 3 * sizeof(float);
	stride += 2 * sizeof(float);

	if( !valid_flag || this->vertices_count != vertices_count )
	{
		if ( vertex_buffer )
			vertex_buffer->Release(); vertex_buffer = 0;

		this->vertices_count = vertices_count;
		if( vertices_count == 0 )
		{
			valid_flag = true;
			return true;
		}

		HRESULT hr;
		if( dynamic )
			hr = hardware->get_device()->CreateVertexBuffer( stride*vertices_count, D3DUSAGE_WRITEONLY/* | D3DUSAGE_DYNAMIC*/, vb_declaration, D3DPOOL_DEFAULT, &vertex_buffer, NULL );
		else
			hr = hardware->get_device()->CreateVertexBuffer( stride*vertices_count, D3DUSAGE_WRITEONLY, vb_declaration, D3DPOOL_MANAGED, &vertex_buffer, NULL );
		//		logs::log() << "create_internal_vertex_buffer() result: " << hr << "\n";
		if ( !vertex_buffer )
			throw Gluck(__FUNCTION__, "create_internal_vertex_buffer() failed");
	}
	if( vertices_count == 0 )
	{
		valid_flag = true;
		return true;
	}

	DWORD size = 0;
	float * data = 0;
	HRESULT hr = vertex_buffer->Lock( 0, 0, (void**) &data, /*dynamic ? D3DLOCK_DISCARD :*/ 0 );
	if ( FAILED(hr) )
		throw Gluck(__FUNCTION__, "vertex_buffer->Lock() failed");

	SimpleArray<float>::const_iterator posit = positions.begin();
	SimpleArray<float>::const_iterator norit = normals.begin();
	SimpleArray<float>::const_iterator colit = colors.begin();
	SimpleArray<float>::const_iterator uv0it = uv0.begin();
	for( int i = 0; i < vertices_count; ++i )
	{
		*data++ = *posit++;
		*data++ = *posit++;
		*data++ = *posit++;

		if( normals.size() != vertices_count*3 )
		{
			//just skip it, only color information supplied then
//			*data++ = 0;
//			*data++ = 0;
//			*data++ = 1;
		}
		else
		{
			*data++ = *norit++;
			*data++ = *norit++;
			*data++ = *norit++;
		}

		if( colors.size() != vertices_count*4 )
		{
			unsigned char * char_data = (unsigned char *)data;
			*char_data++ = 255;
			*char_data++ = 255;
			*char_data++ = 255;
			*char_data++ = 255;

			// dav: static analyzer told me to remove dereferencing from here.
			data++;
		}
		else
		{
			unsigned char * char_data = (unsigned char *)data;
			*char_data++ = math::clamp<float>( (*(colit+2))*255, 0, 255 );
			*char_data++ = math::clamp<float>( (*(colit+1))*255, 0, 255 );
			*char_data++ = math::clamp<float>( (*colit)*255, 0, 255 );
			colit += 3;
			*char_data++ = math::clamp<float>( (*colit++)*255, 0, 255 );

			// dav: static analyzer told me to remove dereferencing from here.
			data++;
		}

		if( uv0.size() != vertices_count*2 )
		{
			*data++ = 0;
			*data++ = 0;
		}
		else
		{
			*data++ = *uv0it++;
			*data++ = *uv0it++;
		}
	}
	hr = vertex_buffer->Unlock();

	valid_flag = true;
	return true;

}

void VertexBuffer::set_primitives_type(PrimitivesType primitiveType)
{
	primitives_type = primitiveType;
}

D3DPRIMITIVETYPE VertexBuffer::get_internal_primitives_type()
{
	switch(primitives_type)
	{
	case LINESLIST:
		return D3DPT_LINELIST;

	case LINESSTRIP:
		return D3DPT_LINESTRIP;

	case TRIANGLESLIST:
		return D3DPT_TRIANGLELIST;

	case TRIANGLESSTRIP:
		return D3DPT_TRIANGLESTRIP;
	}

	return D3DPT_TRIANGLELIST;
}

int VertexBuffer::get_primitive_size()
{
	switch(primitives_type)
	{
	case LINESLIST:
		return 2;

	case LINESSTRIP:
		return 2;

	case TRIANGLESLIST:
		return 3;

	case TRIANGLESSTRIP:
		return 3;
	}

	return 3;
}

bool VertexBuffer::render( int vertex_pos, int primitive_count )
{
	if( vertices_count == 0 || !hardware->is_operational() )
		return true;
	// TODO - remove logs make caller understand result of the call
	if( vertex_pos + get_primitive_size() * primitive_count > vertices_count )
	{
		logs::log() << logs::error << "Vertex buffer overflow - " << primitive_count << " primitives at position " << vertex_pos << " while the total number of vertices is " << vertices_count << "\n";
		return false;
	}
	HRESULT hr;

	if( vb_declaration != (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1) )
	{
		hardware->get_device()->SetFVF( vb_declaration );
		if( !(vb_declaration & D3DFVF_NORMAL) || hardware->get_current_render_list()->lights.size() == 0 )
		{
			hardware->get_device()->SetRenderState( D3DRS_LIGHTING, FALSE );
		}
	}
	hr = hardware->get_device()->SetStreamSource( 0, vertex_buffer, 0, stride );
//9 hr = hardware->get_device()->SetVertexDeclaration( hardware->get_shader_declaration_normal() );
	hr = hardware->get_device()->DrawPrimitive( get_internal_primitives_type(), vertex_pos, primitive_count );
	if( vb_declaration != (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1) )
	{
		hardware->get_device()->SetFVF( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 );

		if( !(vb_declaration & D3DFVF_NORMAL) || hardware->get_current_render_list()->lights.size() == 0 )
		{
			hardware->get_device()->SetRenderState( D3DRS_LIGHTING, TRUE );
		}
	}
	return true;
}

bool VertexBuffer::render( hard::IndexBuffer * ib, int index_pos, int primitive_count, int vertex_pos, int vertex_count )
{
	if( vertices_count == 0 || !hardware->is_operational() )
		return true;
	HRESULT hr;

	if( vb_declaration != (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1) )
		hardware->get_device()->SetFVF( vb_declaration );
	hr = hardware->get_device()->SetStreamSource( 0, vertex_buffer, 0, stride );
//	hr = hardware->get_device()->SetVertexDeclaration( hardware->get_shader_declaration_normal() );
	bool ok = ib->render( index_pos, primitive_count, vertex_pos, vertex_count);
	if( vb_declaration != (D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1) )
		hardware->get_device()->SetFVF( D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX1 );
	return ok;
}

bool VertexBuffer::render( hard::VertexBuffer * vb2, int vertex_pos, int primitive_count )
{
	if( vertices_count == 0 || !hardware->is_operational() )
		return true;

	VertexBuffer * vb2_our = static_cast<VertexBuffer * >( vb2 ); // BUG :)
	if( vertices_count != vb2_our->vertices_count )
	{
		logs::log() << logs::error << "Topology of the morph buffers differ:  " << vertices_count << " : " << vb2_our->vertices_count << " vertices\n";
		return false;
	}
	if( vertices_count == 0 )
		return true;
	// TODO - remove logs make caller understand result of the call
	if( vertex_pos + get_primitive_size() * primitive_count > vertices_count )
	{
		logs::log() << logs::error << "Vertex buffer overflow - " << primitive_count << " primitives at position " << vertex_pos << " while the total number of vertices is " << vertices_count << "\n";
		return false;
	}
	HRESULT hr;
	hr = hardware->get_device()->SetStreamSource( 0, vertex_buffer, 0, stride );
	hr = hardware->get_device()->SetStreamSource( 1, vb2_our->vertex_buffer, 0, vb2_our->stride );
//9	hr = hardware->get_device()->SetVertexDeclaration( hardware->get_shader_declaration_morph() );
	hr = hardware->get_device()->DrawPrimitive( get_internal_primitives_type(), vertex_pos, primitive_count );
	return true;
}

bool VertexBuffer::render( hard::VertexBuffer * vb2, hard::IndexBuffer * ib, int index_pos, int primitive_count, int vertex_pos, int vertex_count )
{
	if( vertices_count == 0 || !hardware->is_operational() )
		return true;
	VertexBuffer * vb2_our = static_cast<VertexBuffer * >( vb2 ); // BUG :)
	if( vertices_count != vb2_our->vertices_count )
	{
		logs::log() << logs::error << "Topology of the morph buffers differ:  " << vertices_count << " : " << vb2_our->vertices_count << " vertices\n";
		return false;
	}
	if( vertices_count == 0 )
		return true;
	HRESULT hr;
	hr = hardware->get_device()->SetStreamSource( 0, vertex_buffer, 0, stride );
	hr = hardware->get_device()->SetStreamSource( 1, vb2_our->vertex_buffer, 0, vb2_our->stride );
//9	hr = hardware->get_device()->SetVertexDeclaration( hardware->get_shader_declaration_morph() );
	return ib->render( index_pos, primitive_count, vertex_pos, vertex_count);
}

void VertexBuffer::invalidate()
{
	if( dynamic )
	{
		valid_flag = false;
		if( vertex_buffer )
			int ref_count = vertex_buffer->Release();
		vertex_buffer = 0;
	}
//	valid_flag = false; we are managed :)
}

void VertexBuffer::release()
{
	valid_flag = false;

	if( vertex_buffer )
		int ref_count = vertex_buffer->Release();
	vertex_buffer = 0;
}

} // namespace directx8
} // namespace hard
} // namespace lib3d
} // namespace liba

